package skyblue;

import java.util.LinkedList;
import java.util.Stack;

import featuremodel.FeatureRep;
import featuremodel.method.MethodChecker;

public class Solver {
	private SkyBlue system;

	public Solver(SkyBlue skyblue) {

		this.system = skyblue;

	}

	public void addConstraint(Constraint cn) {

		// TODO ����������ж��Ƿ�������Ĵ���

		if (cn.getSelectedMethod() == null) {
			// TODO ԭ��unenforced��constraint������Ϊ�Ҽ���һ��constraint���ɿ���enforced����
			system.clearWorklist();
			cn.getMark().clear();

			// Register that constraint to all of it's variables.
			Variable iteri;
			for (int i = 0; i < cn.getVariables().size(); i++) {
				iteri = cn.getVariables().get(i);
				iteri.addConstraint(cn);
			}

			system.pushUnenforcedCn(cn);
			
			System.out.print("*********try to add "+cn.constraintType+"(");
			FeatureRep f;
			for (int q = 0; q < cn.getVariables().size(); q++) {
				f = (FeatureRep) cn.getVariables().get(q).getValue()
						.getValuerep();
				System.out.print(f.getName()+",");
			}
			System.out.println(")***********");
			updateMethodGraph();

			// TODO ����Alexander�ķ������ֲ��ļ��Inconsistency��
			// �����Inconsistent������ֻ��һ��method��ѡ��ʱ���propagation,
			if(system.getExecRootCns().isEmpty()){
				System.out.print("*********failed***********\n");
			}else{
				System.out.print("*********success***********\n");
			}
			new Executor(system);
			
		} else {
			System.out.println("This constraint has allready been enforced!!!");
		}
		
	}

	public void removeConstraint(Constraint cn) {

		// Unregister that constraint to all of it's variables.
		Variable iteri;
		for (int i = 0; i < cn.getVariables().size(); i++) {
			iteri = cn.getVariables().get(i);
			// �����漰����Constraint�о���LinkedList��ã���Ϊ�漰����������ɾ����
			iteri.getConstraints().remove(cn);
		}

		// If the constraint is already enforced
		if (cn.getSelectedMethod() != null) {

			getSystem().clearWorklist();
			getSystem().getUndetVars().clear();
			getSystem().getSubundetVars().clear();

			// unenforce constraint and collect newly-undetermined variables
			Variable iterj = new Variable();
			for (int j = 0; j < cn.getSelectedMethod().getOutputs().size(); j++) {
				iterj = cn.getSelectedMethod().getOutputs().get(j);
				iterj.getDeterminedbys().remove(cn);
				if (iterj.getDeterminedbys().size() == 0) {
					// Q ��������set to be method�Ǹ�ʲô��
					//cn.getSelectedMethod().clearTobeMethodType2();
					//iterj.setWalkStrength(SkyBlueDef.WEAKEST_STRENGTH);
					system.addUndetVars(iterj);
					system.addExecRoot(iterj);
				} else if (cn.equals(iterj.getStrongestcn())) {
					//cn.getSelectedMethod().clearTobeMethodType2();
					//iterj.setWalkStrength(SkyBlueDef.WEAKEST_STRENGTH);
					// caculateWalkaboutStrength(iterj);
					system.getSubundetVars().add(iterj);
					system.addExecRoot(iterj);
				}
			}
			cn.revoke();

			// propagate walkbound downstream of newly undetermined vars
			//propagateWalkStrength(null, true);

			// collect all unenforced cns downstream of undetermined vars
			// with the same or weaker strength than the removed constraint
			collectUnenforced(getSystem().getUndetVars(), getSystem()
					.getSubundetVars(), null, cn.getStrength(), true);

			// try enforcing the collected unenforced constraints
			updateMethodGraph();
			// TODO ����
			// Alexander�ķ������ֲ��ļ��Inconsistency,�����Inconsistent����propagation
			new Executor(system);

		}

	}

	public void caculateWalkaboutStrength(Variable var) {
		Constraint cn;
		Strength newStrength;
		Strength mtStrength;
		Method iterj;
		LinkedList<Variable> currentOutputs;
		LinkedList<Constraint> cns = new LinkedList<Constraint>();

		for (int i = 0; i < var.getDeterminedbys().size(); i++) {
			cn = var.getDeterminedbys().get(i);
			currentOutputs = cn.getSelectedMethod().getOutputs();
			newStrength = cn.getStrength();
			for (int j = 0; j < cn.getMethods().size(); j++) {
				iterj = cn.getMethods().get(j);

				iterj.setTobeMethodType2();

				if (!iterj.getOutputs().contains(var)) {
					mtStrength = maxOut(iterj, currentOutputs);
					if (mtStrength.weaker(newStrength))
						newStrength = mtStrength;

				} else {
					if (MethodChecker.checkBeforeConflicts2(var)) {
						currentOutputs.remove(var);
						mtStrength = maxOut(iterj, currentOutputs);
						if (mtStrength.weaker(newStrength))
							newStrength = mtStrength;
					}
				}
				iterj.clearTobeMethodType2();
			}

			cns = var.getDeterminedbys();
			if (cns.size() == 1) {
				var.setWalkStrength(newStrength);
				var.setStrongestcn(cn);
			} else if (cns.size() > 1) {
				if (var.getWalkStrength().weaker(newStrength)) {
					var.setWalkStrength(newStrength);
					var.setStrongestcn(cn);
				}
			}
		}

	}

	public void changeConstraintStrength(Constraint cn, Strength strength) {
		Strength oldStrength = cn.getStrength();
		cn.setStrength(strength);
		getSystem().clearWorklist();
		if (oldStrength.weaker(strength)) {
			if (cn.getSelectedMethod() != null) {
				// strengthening an enforced cn: just update walk strength
				//propagateWalkStrength(cn, false);
			} else {
				// strengthening an unenforced cn: it may now be enforcible
				getSystem().pushUnenforcedCn(cn);
				updateMethodGraph();
				new Executor(system);
			}
		} else if (oldStrength.stronger(strength)) {
			if (cn.getSelectedMethod() != null) {
				// weakening an enforced cn: other constraints may now be
				// enforcible
				//propagateWalkStrength(cn, false);
				// empty varlist means that we do not want to handle the
				// undetermined constraints
				// Stack<Variable> emptyVarList = new Stack<Variable>();
				// LinkedList<Variable> emptySubVarList = new
				// LinkedList<Variable>();
				collectUnenforced(null, null, cn, oldStrength, true);
				updateMethodGraph();
				new Executor(system);
			} else {
				// weakening an unenforced cn: do nothing
			}

		} else {
			// strength unchanged: do nothing
		}

	}

	public void updateMethodGraph() {
		while (!system.getUnenforcedCns().isEmpty()) {
			// TODO Now we did not pop the strongest constraint to try to
			// enforce
			// May be we can do it later to improve the performance
			Constraint cn = getSystem().getUnenforcedCns().pop();

			getSystem().getUndetVars().clear();
			getSystem().getSubundetVars().clear();

			MVine mvine = new MVine(getSystem(), cn);
			if (mvine.isOk()) {
				// withUndetVars? withSubUndetVars?
				//propagateWalkStrength(cn, true);
				// ���һ�������ʾ�Ƿ�collectEqualStrength
				// collectUnenforced û���õ��κ�propagateWalkStrength�Ľ��
				FeatureRep f;
				System.out.print("constriant \""+cn.getConstraintType()+"(");
				for (int q = 0; q < cn.getVariables().size(); q++) {
					f = (FeatureRep) cn.getVariables().get(q).getValue()
							.getValuerep();
					System.out.print(f.getName()+",");
				}
				//System.out.println(")\" is enforced.");
				collectUnenforced(getSystem().getUndetVars(), getSystem()
						.getSubundetVars(), cn, cn.getStrength(), false);
			}
		}

	}

	// STARTS of the strength propagation
	public void propagateWalkStrength(Constraint cn, boolean withUndetVar) {
		Mark propMark = new Mark();
		Stack<Variable> varStack = getSystem().getUndetVars();
		LinkedList<Variable> subVarStack = getSystem().getSubundetVars();
		Variable var;
		getSystem().getPlan().clear();
		// ��addtoplan�У�
		if (withUndetVar) {
			while (!varStack.isEmpty()) {
				var = (Variable) varStack.pop();
				var.setWalkStrength(SkyBlueDef.WEAKEST_STRENGTH);
				getSystem().addToPlan(var, propMark);
			}
			// FM
			while (!subVarStack.isEmpty()) {
				var = (Variable) subVarStack.pop();
				var.setWalkStrength(SkyBlueDef.WEAKEST_STRENGTH);
				getSystem().addToPlan(var, propMark);
			}
		}

		if (cn != null) {
			getSystem().addToPlan(cn, propMark);
		}

		Variable iteri;
		while (!system.getPlan().isEmpty()) {
			cn = system.getPlan().pop();
			// Handle the cycle in the propagation walk plan
			for (int i = 0; i < cn.getVariables().size(); i++) {
				iteri = cn.getVariables().get(i);
				// FM
				boolean flag = false;
				if (!cn.getSelectedMethod().isOutput(iteri)) {
					if (iteri.getDeterminedbys().size() > 0) {
						for (int j = 0; j < iteri.getDeterminedbys().size(); j++) {
							if (iteri.getDeterminedbys().get(j).getMark()
									.getMyMark() == (propMark.getMyMark())) {
								flag = true;
							}
						}
					}
					if (flag) {
						iteri.setWalkStrength(SkyBlueDef.WEAKEST_STRENGTH);
					}
				}
			}
			computeWalkaboutStrengths(cn);
			cn.getMark().clear();

		}
	}

	public void computeWalkaboutStrengths(Constraint cn) {
		// Calculate the walk about strength of the variables which are
		// currently
		// determined by cn
		LinkedList<Variable> currentOutputs = cn.getSelectedMethod()
				.getOutputs();
		Variable iteri = new Variable();
		Method iterj;
		Strength newStrength;
		Strength mtStrength;
		// FM
		LinkedList<Constraint> cns = new LinkedList<Constraint>();

		for (int i = 0; i < currentOutputs.size(); i++) {
			iteri = currentOutputs.get(i);
			newStrength = cn.getStrength();
			for (int j = 0; j < cn.getMethods().size(); j++) {
				iterj = cn.getMethods().get(j);

				iterj.setTobeMethodType2();

				if (!iterj.getOutputs().contains(iteri)) {
					mtStrength = maxOut(iterj, currentOutputs);
					if (mtStrength.weaker(newStrength))
						newStrength = mtStrength;

				} else {
					if (MethodChecker.checkBeforeConflicts2(iteri)) {
						currentOutputs.remove(iteri);
						mtStrength = maxOut(iterj, currentOutputs);
						if (mtStrength.weaker(newStrength))
							newStrength = mtStrength;
					}
				}
				iterj.clearTobeMethodType2();
			}
			// FM
			// cns = iteri.getDeterminedbys();
			// if (cns.size() > 1) {
			// cns.remove(cn);
			// for (int k = 0; k < cns.size(); k++) {
			// if (iteri.getWalkStrength().weaker(newStrength)) {
			// iteri.setWalkStrength(newStrength);
			// iteri.setStrongestcn(cn);
			// }
			// }
			// } else {
			// iteri.setWalkStrength(newStrength);
			// iteri.setStrongestcn(cn);
			//
			// }

			cns = iteri.getDeterminedbys();
			if (cns.size() == 1) {
				iteri.setWalkStrength(newStrength);
				iteri.setStrongestcn(cn);
			} else if (cns.size() > 1) {
				if (iteri.getWalkStrength().weaker(newStrength)) {
					iteri.setWalkStrength(newStrength);
					iteri.setStrongestcn(cn);
				}
			}
		}
	}

	public Strength maxOut(Method iterj, LinkedList<Variable> currentOutputs) {
		Strength maxStrength = SkyBlueDef.WEAKEST_STRENGTH;
		Variable iteri = new Variable();

		for (int i = 0; i < iterj.getOutputs().size(); i++) {
			iteri = iterj.getOutputs().get(i);
			if (!currentOutputs.contains(iteri)) {
				if (MethodChecker.checkBeforeConflicts2(iteri)) {
					if (maxStrength.weaker(iteri.getWalkStrength()))
						maxStrength = iteri.getWalkStrength();
				}

			}
		}
		return maxStrength;
	}

	// ENDS of the strength propagation

	// START collect unenforced constraints
	public void collectUnenforced(Stack<Variable> undetVars,
			LinkedList<Variable> subundetVars, Constraint rootCn,
			Strength strength, boolean collectEqualStrength) {
		Mark doneMark = new Mark();
		Variable var;
		// the undetermined vars and the output of the current method is used to
		// collect the unenforced constraints
		while (!undetVars.empty()) {
			var = undetVars.pop();
			collectUnenforcedMark(var, strength, collectEqualStrength, doneMark);

		}
		// FM
		while (!subundetVars.isEmpty()) {
			var = subundetVars.pop();
			collectUnenforcedMark(var, strength, collectEqualStrength, doneMark);

		}
		if (rootCn != null) {
			for (int i = 0; i < rootCn.getSelectedMethod().getOutputs().size(); i++) {
				var = rootCn.getSelectedMethod().getOutputs().get(i);
				// �����ҵ���revoke������Щconstraints
				// TODO
				// ��ô����ʵ������һЩ���࣬ĳЩvariable��method�仯֮�󣬻��Ǳ�ͬһ��constraintdetermine
				// �Ժ��иĽ�����
				collectUnenforcedMark(var, strength, collectEqualStrength,
						doneMark);
			}
		}
	}

	public void collectUnenforcedMark(Variable var, Strength strength,
			boolean collectEqualStrength, Mark doneMark) {

		Constraint cn;
		Variable iteri;
		
		LinkedList<Constraint> cnlist = var.getConstraints();

		while (!cnlist.isEmpty()) {
			cn = cnlist.pollLast();
			// FM output��input�໥ת��
			if (cn.getMark().getMyMark() != doneMark.getMyMark()) {
				cn.setMark(doneMark);

				if (cn.enforced()) {
					for (int i = 0; i < cn.getSelectedMethod().getOutputs()
							.size(); i++) {
						iteri = cn.getSelectedMethod().getOutputs().get(i);
						if (!iteri.equals(var)) {
							collectUnenforcedMark(iteri, strength,
									collectEqualStrength, doneMark);

						}
					}

				} else if (cn.getStrength().weaker(strength)
						|| (collectEqualStrength && cn.getStrength().equals(
								strength))) {
					getSystem().pushUnenforcedCn(cn);
				}
			}
		}

	}

	// END of the collect unenforced constraints��

	public SkyBlue getSystem() {
		return system;
	}

	public void setSystem(SkyBlue system) {
		this.system = system;
	}

}
